﻿using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using JetBrains.DocumentModel;
using JetBrains.ProjectModel;
using JetBrains.ReSharper.Feature.Services.Bulbs;
using JetBrains.ReSharper.Feature.Services.CSharp.Bulbs;
using JetBrains.ReSharper.Feature.Services.LiveTemplates.Hotspots;
using JetBrains.ReSharper.Feature.Services.LiveTemplates.Macros.Implementations;
using JetBrains.ReSharper.Intentions.Extensibility;
using JetBrains.ReSharper.Intentions.Util;
using JetBrains.ReSharper.LiveTemplates;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.Caches;
using JetBrains.ReSharper.Psi.CSharp.Tree;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.TextControl;
using JetBrains.Util;
using MugenMvvmToolkit.ReSharper.Infrastructure;

namespace MugenMvvmToolkit.ReSharper.Views.ContextActions
{
    [ContextAction(Name = "ImplementViewInterfaceAction", Group = "C#", Priority = 100)]
    public class ImplementViewInterfaceAction : ContextActionImplBase
    {
        #region Fielsd

        private static readonly DocumentRange[] EmptyRange = new DocumentRange[0];
        private IClassDeclaration _currentClassDeclaration;

        #endregion

        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="ImplementViewInterfaceAction" /> class.
        /// </summary>
        public ImplementViewInterfaceAction([NotNull] ICSharpContextActionDataProvider provider)
            : base(provider)
        {
        }

        #endregion

        #region Implementation of IContextAction

        /// <summary>
        ///     Check if this action is available at the constructed context.
        ///     Actions could store precalculated info in <paramref name="cache" /> to share it between different actions
        /// </summary>
        /// <returns>
        ///     true if this bulb action is available, false otherwise.
        /// </returns>
        public override bool IsAvailable(IUserDataHolder cache)
        {
            if (Provider.SelectedElement == null)
                return false;
            _currentClassDeclaration = Provider.SelectedElement.Parent as IClassDeclaration;
            if (_currentClassDeclaration == null || !_currentClassDeclaration.IsOfType(TypeProvider.FrameworkElement))
                return false;

            Actions = new List<IBulbAction>();
            bool hasViewModelAttribute = _currentClassDeclaration.HasAttributeInstance(TypeProvider.ViewModelAttribute);
            bool isPage = _currentClassDeclaration.IsOfType(TypeProvider.Page);
            bool hasPageAttribute = _currentClassDeclaration.HasAttributeInstance(TypeProvider.PageViewModelAttribute);
            bool isSpecificView = false;
            foreach (var viewInfoModel in TypeProvider.GetBaseViewTypes()
                .Where(model => model.ViewType.IsResolved)
                .OrderByDescending(model => model.Priority))
            {
                if (viewInfoModel.UiViewType != null)
                {
                    if (viewInfoModel.UiViewType.IsResolved && _currentClassDeclaration.IsOfType(viewInfoModel.UiViewType))
                    {
                        if (!isSpecificView)
                            Actions.Clear();
                        UpdateActions(viewInfoModel.ViewType, hasViewModelAttribute, viewInfoModel.Name);
                        isSpecificView = true;
                    }
                    continue;
                }
                if (!isSpecificView)
                    UpdateActions(viewInfoModel.ViewType, hasViewModelAttribute, viewInfoModel.Name);
            }
            if (Actions.Count == 0)
            {
                if (isPage && !hasPageAttribute)
                    Actions.Add(new DelegateBulbAction((solution, indicator) => AddAttribute(solution, true), UiResources.AddPageVmAttribute));
            }
            if (!ToolkitExtensions.IsImplementGenericView(_currentClassDeclaration))
            {
                IDeclaredType genericViewInterface = TypeProvider.ViewInterfaceGeneric;
                ITypeElement typeElement = genericViewInterface.GetTypeElement();
                if (typeElement != null)
                {
                    genericViewInterface = TypeFactory.CreateType(typeElement, TypeProvider.Object);
                    if (TypeExtensions.TryResolve(ref genericViewInterface))
                    {
                        Actions.Add(new DelegateBulbAction(
                                    (solution, indicator) => AddInterface(solution, genericViewInterface, isPage,
                                        isPage ? hasPageAttribute : hasViewModelAttribute), UiResources.ImplementGenericView));
                    }
                }
            }
            return Actions.Count != 0;
        }

        #endregion

        #region Methods

        private void UpdateActions(IDeclaredType interfaceType, bool hasViewModelAttribute, string name)
        {
            if (!TypeExtensions.TryResolve(ref interfaceType))
                return;
            if (_currentClassDeclaration.IsOfType(interfaceType))
            {
                if (Actions.Count == 0 && !hasViewModelAttribute)
                    Actions.Add(new DelegateBulbAction((solution, indicator) => AddAttribute(solution, false), UiResources.AddVmAttribute));
                return;
            }
            Actions.Add(
                new DelegateBulbAction(
                    (solution, indicator) => AddInterface(solution, interfaceType, false, hasViewModelAttribute),
                    string.Format(UiResources.ImplementViewFormat, name)));
        }

        private Action<ITextControl> AddInterface(ISolution solution, IDeclaredType interfaceType, bool isPage,
            bool hasAttribute)
        {
            Func<DocumentRange> getAttributeRange = () => DocumentRange.InvalidRange;
            if (!hasAttribute)
                getAttributeRange = AddAttributeInternal(isPage);
            var getInterfaceRange = AddInterfaceInternal(interfaceType);
            return control =>
            {
                DocumentRange range = getAttributeRange();
                var interfaceRange = getInterfaceRange();
                if (range.IsValid() || !interfaceRange.IsEmpty())
                {
                    var hotspotInfo = new HotspotInfo(GetViewModelsTemplateField(), interfaceRange.Concat(range));
                    LiveTemplatesManager.TryExecuteHotspotDefault(solution, control, hotspotInfo);
                }
            };

        }

        private Action<ITextControl> AddAttribute(ISolution solution, bool isPage)
        {
            var getRange = AddAttributeInternal(isPage);
            return control =>
            {
                DocumentRange documentRange = getRange();
                if (documentRange.IsValid())
                {
                    LiveTemplatesManager.TryExecuteHotspotDefault(solution, control, new HotspotInfo(GetViewModelsTemplateField(), documentRange));
                }
            };
        }

        [NotNull]
        private Func<IList<DocumentRange>> AddInterfaceInternal(IDeclaredType interfaceType)
        {
            _currentClassDeclaration.AddSuperInterface(interfaceType, false);
            if (interfaceType.IsGenericClass())
            {
                var propertyDeclaration = (IPropertyDeclaration)Provider
                    .ElementFactory
                    .CreateTypeMemberDeclaration(CodeTemplateConstants.AutoProperty, TypeProvider.Object, "ViewModel");
                propertyDeclaration = _currentClassDeclaration.AddClassMemberDeclaration(propertyDeclaration);
                return () =>
                {
                    var viewTypeUsage = _currentClassDeclaration
                    .SuperTypeUsageNodes
                    .OfType<IUserDeclaredTypeUsage>()
                    .SingleOrDefault(usage => usage.TypeName.ShortName == interfaceType.GetClrName().ShortName && !usage.TypeName.TypeArgumentList.IsEmpty());
                    if (viewTypeUsage == null)
                        return EmptyRange;
                    var documentRange = viewTypeUsage.TypeName
                        .TypeArgumentList
                        .TypeArgumentNodes[0]
                        .GetDocumentRange();

                    DocumentRange range = propertyDeclaration.TypeUsage.GetDocumentRange();
                    return new[] { documentRange, range };
                };
            }
            return () => EmptyRange;
        }

        [NotNull]
        private Func<DocumentRange> AddAttributeInternal(bool isPage)
        {
            IDeclaredType attributeType = isPage
                ? TypeProvider.PageViewModelAttribute
                : TypeProvider.ViewModelAttribute;

            IAttribute attribute = Provider.ElementFactory
                .CreateAttribute(attributeType.GetTypeElement(), new[] { new AttributeValue(TypeProvider.Object) }, new Pair<string, AttributeValue>[0]);
            attribute = _currentClassDeclaration.AddAttributeAfter(attribute, null);
            ContextActionUtils.FormatWithDefaultProfile((ITreeNode)(AttributeSectionNavigator.GetByAttribute(attribute) ?? (object)attribute));
            return () =>
            {
                var typeOfExpression = attribute.ConstructorArgumentExpressions.SingleOrDefault() as ITypeofExpression;
                if (typeOfExpression == null)
                    return DocumentRange.InvalidRange;

                DocumentRange documentRange = typeOfExpression.TypeName.GetDocumentRange();
                return documentRange;
            };
        }

        private TemplateField GetViewModelsTemplateField()
        {
            string viewName = _currentClassDeclaration.DeclaredName;
            ISymbolScope symbolScope = Provider
                .PsiServices
                .Symbols
                .GetSymbolScope(TypeProvider.PsiModule, TypeProvider.ResolveContext, true, false);

            var list = new List<string>();
            if (viewName.EndsWith("View"))
                list.Add(viewName + "Model");
            else if (viewName.EndsWith("V"))
                list.Add(viewName + "M");
            else
            {
                list.Add(viewName + "Vm");
                list.Add(viewName + "ViewModel");
            }

            var declaredTypes = new List<IType>();
            foreach (string vmName in list)
            {
                IClrDeclaredElement[] elements = symbolScope.GetElementsByShortName(vmName);
                IEnumerable<IDeclaredType> vms = elements
                    .OfType<IClass>()
                    .Where(@class => !@class.IsAbstract)
                    .Select(TypeFactory.CreateType)
                    .Where(type => type != null && type.IsOfType(TypeProvider.ViewModelInterface));
                declaredTypes.AddRange(vms);
            }
            IHotspotExpression expression = declaredTypes.Count == 0
                ? (IHotspotExpression)new NameSuggestionsExpression(list)
                : new TypeTemplateExpression(declaredTypes, Provider.PsiModule, Provider.PsiFile.Language);
            return new TemplateField("CHANGETYPE", expression, 0);
        }

        #endregion
    }
}